/* 
 * Gneat (http://gneat.org)
 *
 * (C) Copyright 2011 and beyond - Claudio Stella, all rights reserved.
 *
 * This file is part of Gneat.
 *
 * Gneat is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (LGPL)
 * as published by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Gneat is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * and the GNU General Public License, for completeness, along with Gneat.  
 * If not, see <http://www.gnu.org/licenses/>.
 */
package org.neatshell.gneat.client.view.dead.core;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import javax.inject.Inject;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import nu.validator.htmlparser.common.XmlViolationPolicy;
import nu.validator.htmlparser.dom.HtmlDocumentBuilder;

import org.neatshell.gneat.client.GneatCore;
import org.neatshell.gneat.client.event.Events;
import org.neatshell.gneat.client.handler.Handler;
import org.neatshell.gneat.client.presenter.core.Presenter;
import org.neatshell.gneat.client.presenter.core.Presenter.IView;
import org.neatshell.gneat.client.presenter.core.manipulation.css.Css;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class DeadView implements Presenter.IView {
	private static Document $doc;

	private HashMap<String, String> _properties = null;
	private HashMap<String, String> _styles     = null;
	
	Node _node;

	@Inject
	protected DeadView() {}

	@Override
	public void addEventHandler(String event, Handler handler) {}
	
	@Override
	public void append(IView view) {
		_node.appendChild((Node) ((DeadView) view).getNode());
	}

	@Override
	public void append(String html) {
		try {
			HtmlDocumentBuilder htmlDocument = new HtmlDocumentBuilder();
			htmlDocument.setCommentPolicy(XmlViolationPolicy.ALLOW);
			htmlDocument.setIgnoringComments(false);

			//workaround to parse stand-alone comment nodes
			html = "<p></p>"+html;

			Document doc = htmlDocument.parse(new InputSource(new ByteArrayInputStream(html.getBytes())));
			Node toAppend = doc.getDocumentElement().getElementsByTagName("body").item(0);
			
			_node.getOwnerDocument().adoptNode(toAppend);
			NodeList nodes = toAppend.getChildNodes();
			for (int i = 1; i<nodes.getLength(); i++) {
				_node.appendChild(nodes.item(i).cloneNode(true));
			}
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}		
	}

	@Override
	public IView clone() {
		IView cloned = GneatCore.getCoreInjector().getView().setNode(_node.cloneNode(false));

		if (_styles != null) {
			Iterator<String> css_it = _styles.keySet().iterator();
			while (css_it.hasNext()) {
				String name = (String) css_it.next();
				cloned.setCss(name, _styles.get(name));
			}
		}
		if (_properties != null) {
			Iterator<String> property_it = _properties.keySet().iterator();
			while (property_it.hasNext()) {
				String property = (String) property_it.next();
				cloned.setProperty(property, _properties.get(property));
			}
		}
		return cloned;
	}

	@Override
	public void detach() {
		if (!getNodeName().equals("body")) {
			Node parent = _node.getParentNode();
			if (parent != null) parent.removeChild(_node);
		}
	}
	
	@Override
	public String getAttribute(String name) {
		if (((Element) _node).hasAttribute(name)) {
			return ((Element) _node).getAttribute(name);
		}
		else return null;
	}

	@Override
	public String getClassName() {
		return getElement().getAttribute("class");
	}

	@Override
	public String getCss(String name) { 
		if (_styles != null) return _styles.get(name);
		return null;
	}
	
	protected static Document getDocument() {
		if ($doc == null) {
			try {
				$doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
			} catch (ParserConfigurationException e) {
				e.printStackTrace();
			}
		}
		return $doc;
	}

	private Element getElement() {
		return (Element) _node;
	}
		
	@Override
	public Object getFirstChildNode() {
		return _node.getFirstChild();
	}
	
	@Override
	public Object getLastChildNode() {
		return _node.getLastChild();
	}
	
	@Override
	public Object getNextNode() {
		return _node.getNextSibling();
	}

	@Override
	public Object getNode() {
		return _node;
	}

	@Override
	public Object getParentNode() {
		return _node.getParentNode();
	}
	
	@Override
	public Object getPrevNode() {
		return _node.getPreviousSibling();
	}
	
	@Override
	public String getProperty(String propName) {
		return _properties != null ? _properties.get(propName) : null;
	}

	@Override
	public String html() {
		String html = "";
		if (_node.hasChildNodes()) {
			try {
				
				TransformerFactory transformerFactory = TransformerFactory.newInstance();
				Transformer transformer;
				transformer = transformerFactory.newTransformer();
				StringWriter buffer = new StringWriter();

				transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
				transformer.setOutputProperty(OutputKeys.METHOD, "html");

				String ghostName = "a"+new BigInteger(130, new Random()).toString(30)+"a";
				Node ghost = getDocument().createElement(ghostName);

				for (int i=0; i<_node.getChildNodes().getLength(); i++) {
					ghost.appendChild(_node.getChildNodes().item(i).cloneNode(true));
				}
				
				transformer.transform(new DOMSource(ghost), new StreamResult(buffer));
				
				html = buffer.toString();
				html = html.replace("<"+ghostName+">", "");
				html = html.replace("</"+ghostName+">", "");				
				html = html.replace(" xmlns=\"http://www.w3.org/1999/xhtml\"", "");
				
			} catch (TransformerConfigurationException e) {
				e.printStackTrace();
			} catch (TransformerException e) {
				e.printStackTrace();
			}
		}
		return html;
	}

	@Override
	public void html(String html) {
		text("");
		append(html);
	}
	
	@Override
	public void insertBefore(IView newChild, IView refChild) {
		Node newEl = (Node) newChild.getNode();
		Node refEl = refChild != null ? (Node) refChild.getNode() : null;
		_node.insertBefore(newEl, refEl);
	}

	@Override
	public void insertBefore(String html, IView refChild) {
		Node refEl = refChild != null ? (Node) refChild.getNode() : null;
				
		try {
			HtmlDocumentBuilder htmlDocument = new HtmlDocumentBuilder();
			htmlDocument.setCommentPolicy(XmlViolationPolicy.ALLOW);
			htmlDocument.setIgnoringComments(false);

			//workaround to parse stand-alone comment nodes
			html = "<p></p>"+html;
			
			Document doc = htmlDocument.parse(new InputSource(new ByteArrayInputStream(html.getBytes())));
			Node toInsert = doc.getDocumentElement().getElementsByTagName("body").item(0);
			_node.getOwnerDocument().adoptNode(toInsert);			
			NodeList nodes = toInsert.getChildNodes();

			for (int i = 1; i<nodes.getLength(); i++) {
				_node.insertBefore(nodes.item(i).cloneNode(true), refEl);
			}
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

//	public boolean is(String... filters) {
//		return false;
//	}

	@Override
	public String getNodeName() {
		return _node.getNodeName();
	}

	@Override
	public short getNodeType() {
		return _node.getNodeType();
	}

	@Override
	public String getNodeValue() {
		return _node.getNodeValue();
	}

	@Override
	public void remove() {
		Node parent = _node.getParentNode();
		if (parent != null && !_node.getNodeName().equals("body")) {
			parent.removeChild(_node);
		}
		_node = null;
		if (_properties != null) {
			_properties.clear();
			_properties = null;
		}
		if (_styles != null) {
			_styles.clear();
			_styles = null;
		}
	}

	@Override
	public void removeAttr(String name) {
		getElement().removeAttribute(name);
	}

	@Override
	public void removeEventHandler(List<Handler> handlers) {
		handlers.clear();
	}
	
	@Override
	public void removeProp(String prop) {
		if (_properties != null) _properties.remove(prop);
	}

	@Override
	public void setAttribute(String name, String value) {
		((Element) _node).setAttribute(name, value);
	}

	@Override
	public void setClassName(String className) {
		getElement().setAttribute("class", className);
	}

	@Override
	public void setCss(Css css, String value) {
		setCss(css.getName(), value);
	}

	@Override
	public void setCss(String name, String value) {
		if (_styles == null) _styles = new HashMap<String, String>();
		_styles.put(name, value);
		//TODO remove the variable _style and use the following code as starting point
		// to work with styles
//		String style = getElement().getAttribute("style");
//		style = style + name + ":" +value+ ";";
//		getElement().setAttribute("style", style);
	}

	@Override
	public IView setNode(Object key) {
//		if (key == null) _node = DeadDocument.get().createElement("element");
//		else if (key instanceof String) _node = DeadDocument.get().createElement((String) key);
		if (key instanceof String) _node = getDocument().createElement((String) key);
		else _node = (Node) key;
		return this;
	}
	
	@Override
	public IView setNode(NodeType nodeType, String nodeValue) {
		if (nodeType == NodeType.ELEMENT) return setNode(nodeValue);
		else if (nodeType == NodeType.TEXT) _node = getDocument().createTextNode(nodeValue);
		else _node = getDocument().createComment(nodeValue);
		return this;
	}

	@Override
	public void setProperty(String propName, Object propValue) {
		if ( _properties == null ) _properties = new HashMap<String, String>();
		_properties.put(propName, propValue.toString());
	}

	@Override
	public void show() {
		if (_styles != null) _styles.remove(Css.DISPLAY);
	}
	
	@Override
	public String text() {
		return getElement().getTextContent();
	}

	@Override
	public void text(String text) {
		getElement().setTextContent(text);
	}
	
	@Override
	public void trigger(String event, HashMap<String,List<Handler>> handlers) {
		if (handlers != null) {
			List<Handler> l = handlers.get(event);
			if (l != null) {
				Iterator<Handler> it_handlers = l.iterator();
				while (it_handlers.hasNext()) Events.trigger(event, it_handlers.next());
			}
		}
	}
}